package storager

import (
	"context"
	"errors"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/qiniu/go-sdk/v7/storagev2/credentials"
	"github.com/qiniu/go-sdk/v7/storagev2/http_client"
	"github.com/qiniu/go-sdk/v7/storagev2/objects"
	"github.com/qiniu/go-sdk/v7/storagev2/uploader"
	"github.com/qiniu/go-sdk/v7/storagev2/uptoken"
	"strings"
	"time"
)

type (
	// QiNiuDrive 七牛上传驱动
	QiNiuDrive struct {
		config *QiNiuConfig
	}
	QiNiuUploadReturnValue struct {
		Key    string `json:"key"`
		Hash   string `json:"hash"`
		Size   int64  `json:"size"`
		Bucket string `json:"bucket"`
		Name   string `json:"name"`
	}
)

func NewQiNiuDrive(config *QiNiuConfig) *QiNiuDrive {
	if config == nil {
		config = &QiNiuConfig{}
	}
	if config.Path != "" {
		config.Path = "uploads"
	}
	return &QiNiuDrive{config}
}

func (d *QiNiuDrive) Upload(ctx context.Context, file *ghttp.UploadFile) (obj *FileObject, err error) {
	tempDir := gfile.Temp("gf_upload_temp")
	tempName, err := file.Save(tempDir, true)
	if err != nil {
		return nil, err
	}
	tempPath := gfile.Join(tempDir, tempName)
	defer func() {
		_ = gfile.RemoveFile(tempPath)
	}()

	obj = &FileObject{
		Meta: &FileMeta{},
	}
	// 计算文件md5
	obj.Meta.Md5, err = CalcFileMd5(tempPath)
	if err != nil {
		return nil, err
	}

	var (
		returnValue *QiNiuUploadReturnValue
		nowDate     = time.Now().Format("2006/01/02")
		key         = strings.Join([]string{d.config.Path, nowDate, obj.Meta.Md5 + Ext(tempName)}, "/")
	)
	uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{
		Options: http_client.Options{
			Credentials: d.getMac(),
		},
	})
	err = uploadManager.UploadFile(ctx, tempPath, &uploader.ObjectOptions{
		BucketName: d.config.Bucket,
		ObjectName: &key,
		FileName:   file.Filename,
	}, &returnValue)
	if err != nil {
		return nil, err
	}
	obj.Key = returnValue.Key
	obj.Hash = returnValue.Hash
	return
}

func (d *QiNiuDrive) Destroy(ctx context.Context, key string) error {
	objectsManager := objects.NewObjectsManager(&objects.ObjectsManagerOptions{
		Options: http_client.Options{Credentials: d.getMac()},
	})
	bucket := objectsManager.Bucket(d.config.Bucket)
	return bucket.Object(key).Delete().Call(ctx)
}

func (d *QiNiuDrive) Location(ctx context.Context, key string) string {
	key = strings.Trim(key, "/")
	return strings.Trim(d.config.Domain, "/") + "/" + key
}

func (d *QiNiuDrive) GetFileMeta(ctx context.Context, key string) (meta *FileMeta, err error) {
	objectsManager := objects.NewObjectsManager(&objects.ObjectsManagerOptions{
		Options: http_client.Options{Credentials: d.getMac()},
	})
	bucket := objectsManager.Bucket(d.config.Bucket)
	info, err := bucket.Object(key).Stat().Call(ctx)
	if err != nil {
		return nil, err
	}

	meta = &FileMeta{
		Filename: info.Name,
		Size:     info.Size,
		MimeType: info.MimeType,
		Md5:      ByteToString(info.MD5[:]),
	}
	meta.Ext = TypeToExtension(meta.MimeType)
	meta.Kind = ExtensionToKind(meta.Ext)
	return
}

// GetToken 七牛客户端上传凭证
func (d *QiNiuDrive) GetToken(ctx context.Context, backUrl string) (string, error) {
	if backUrl == "" {
		return "", errors.New("backUrl参数必须")
	}
	putPolicy, err := uptoken.NewPutPolicy(d.config.Bucket, time.Now().Add(1*time.Hour))
	if err != nil {
		return "", err
	}
	putPolicy.SetCallbackUrl(backUrl).
		SetCallbackBody(`{"key":"$(key)","hash":"$(etag)","size":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`).
		SetCallbackBodyType("application/json")
	return uptoken.NewSigner(putPolicy, d.getMac()).GetUpToken(ctx)
}

// VerifyCallback 验证上传回调请求是否来自存储服务
func (d *QiNiuDrive) VerifyCallback(ctx context.Context) (bool, error) {
	req := ghttp.RequestFromCtx(ctx)
	return d.getMac().VerifyCallback(req.Request)
}

func (d *QiNiuDrive) getMac() *credentials.Credentials {
	return credentials.NewCredentials(d.config.AccessKey, d.config.SecretKey)
}
